package net.protocol.diameter.avp;

import net.protocol.diameter.dictionary.AVPFormat;

import java.nio.ByteBuffer;
import java.util.Collection;

/**
 * <pre>
 *   Grouped
 *       The Data field is specified as a sequence of AVPs.  Each of these
 *       AVPs follows - in the order in which they are specified -
 *       including their headers and padding.  The AVP Length field is set
 *       to 8 (12 if the 'V' bit is enabled) plus the total length of all
 *       included AVPs, including their headers and padding.  Thus the AVP
 *       length field of an AVP of type Grouped is always a multiple of 4.
 *
 *                                           +---------------------+
 *                                           |    AVP Flag rules   |
 *                                           |----+-----+----+-----|----+
 *                         AVP               |    |     |SHLD| MUST|    |
 *   Attribute Name      Code   Data Type    |MUST| MAY | NOT|  NOT|Encr|
 *   ----------------------------------------|----+-----+----+-----|----|
 *   Proxy-Info          284    Grouped      | M  |     |    | P,V | N  |
 *   Proxy-State         33     OctetString  | M  |     |    | P,V | N  |
 *   Proxy-Host          280    DiamIdent    | M  |     |    | P,V | N  |
 *
 *   &lt;Proxy-Info&gt; ::= &lt; AVP Header: 284 &gt;
 *                   { Proxy-Host }
 *                   { Proxy-State }
 *                 * [ AVP ]
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
public class Grouped extends AbstractAVP<Collection<AVP<?>>> {
	private final Collection<AVP<?>> data;

    /**
     * @param code AVP Code
     * @param avps AVP Data
     */
    public Grouped(int code, Collection<AVP<?>> avps) {
        this(code, DEFAULT_VENDOR_ID, avps);
    }

    /**
     * @param code     AVP Code
     * @param vendorId Vendor-ID
     * @param avps     AVP Data
     */
    public Grouped(int code, int vendorId, Collection<AVP<?>> avps) {
        super(code, vendorId, getDataSize(avps));

        this.data = avps;
    }

    /**
     * Creates a new <code>Grouped</code> with the given AvpFormat,DiameterAVPHeader and source ByteBuffer
     *
     * @param format diameter avp format
     * @param header Diameter AVP Header
     * @param src The buffer from which bytes are to be retrieved
     */
    Grouped(AVPFormat format, DiameterAVPHeader header, ByteBuffer src) {
        super(header, src);

        this.data = AVPFactory.createGroupAVPs(format, src);
    }

    @Override
    public ByteBuffer byteMe(ByteBuffer dst) {
        super.byteMe(dst);

        if (data == null || data.size() == 0)
        	return dst;
        
        for (AVP<?> e : data) {
            e.byteMe(dst);
        }
        return dst;
    }

    @Override
    public Collection<AVP<?>> getData() {
        return data;
    }

	@Override
	public byte[] getDataBytes() {
		ByteBuffer buf = ByteBuffer.allocate(getDataLength());
		buf.flip();
		for (AVP<?> e : data) {
			buf.put(e.getDataBytes());
		}
		return buf.array();
	}
    
    @Override
    public final boolean isGroup() {
        return true;
    }

    @Override
    public final DataFormat getType() {
        return DataFormat.Grouped;
    }

    @Override
    public String getName() {
        return DataFormat.Grouped.name();
    }

    @Override
    protected void detail(StringBuilder buf) {
        if (data == null || data.size() == 0)
            return;
        for (AVP<?> e : data) {
            buf.append("\n  ").append(e);
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        Grouped other = (Grouped) obj;
        if (data == null) {
            if (other.data != null)
                return false;
        } else if (!data.equals(other.data))
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((data == null) ? 0 : data.hashCode());
        return result;
    }

	private static int getDataSize(Collection<? extends AVP<?>> data) {
		if (data == null || data.size() == 0)
			return 0;
		int dataLength = 0;
		for (AVP<?> e : data) {
			dataLength += e.getLength();
		}
		return dataLength;
	}
}
